﻿using System;
using System.Collections.Generic;
using System.Text;
using Sinacor.Infra.UI.Authentication;
using Sinacor.Infra.UI.Common;
using Sinacor.Infra.UI.Security.Services;
using Sinacor.Infra.UI.Authentication.Providers.Infra.Authorization.Authorization;
using System.ServiceModel.Security;
using Sinacor.Infra.UI.Authentication.Providers.Infra.Authorization.User;
using System.ServiceModel;
using Sinacor.Infra.UI.Exceptions;
using PSE.Framework.ProviderLoader.Configuration;
using PSE.Framework.ProviderLoader;
using PSE.Framework.UI.Resources;
using Sinacor.Infra.Common.Security.Authentication.Credentials;
using PSE.Framework.Common.Security.Services.Configuration;

namespace Sinacor.Infra.UI.Authentication.Providers
{
    public class SinacorAuthenticationProvider : BaseProvider, IAuthenticationProvider
    {
        private User _user;

        /// <summary>
        /// Instância da janela de login.
        /// </summary>
        private SinacorWindowLogin _loginWindow;

        /// <summary>
        /// Instância do objeto de resultado da validação
        /// </summary>
        private ValidationResults _validationResults;

        public SinacorAuthenticationProvider(ProviderConfigurationElement configuration)
            : base(configuration)
        {

        }

        /// <summary>
        /// Solicita ao usuário o login e senha para autenticação.
        /// </summary>
        private void AuthenticateUser()
        {
            _loginWindow = new SinacorWindowLogin();
            _loginWindow.Filled += new EventHandler(_loginWindow_Filled);
            _loginWindow.ChangePassword += new EventHandler(_loginWindow_ChangePassword);
            _loginWindow.ForgotPassword += new EventHandler(_loginWindow_ForgotPassword);
            _loginWindow.ShowDialog();
        }

        /// <summary>
        /// Solicita ao usuário o login e senha para autenticação.
        /// </summary>
        private void AuthenticateUser(string loginName)
        {
            _loginWindow = new SinacorWindowLogin();
            _loginWindow.Filled += new EventHandler(_loginWindow_Filled);
            _loginWindow.ChangePassword += new EventHandler(_loginWindow_ChangePassword);
            _loginWindow.ForgotPassword += new EventHandler(_loginWindow_ForgotPassword);
            _loginWindow.ApplyTemplate();

            if (!String.IsNullOrEmpty(loginName))
            {
                _loginWindow.LoginName = loginName;
                _loginWindow.DisableLoginName();
            }
            _loginWindow.ShowDialog();
        }

        #region Validação do login
        /// <summary>
        /// Método disparado após o preenchimento do Login.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _loginWindow_Filled(object sender, EventArgs e)
        {
            string loginName = _loginWindow.LoginName;
            string password = _loginWindow.Password;

            Func<string, string, ValidationResults> assyncCall = new Func<string, string, ValidationResults>(ValidateUser);
            AsyncCallback assyncCallBack = new AsyncCallback(ValidateUserCallBack);

            //Executa a autenticação do usuário e senha de modo assíncrono.
            assyncCall.BeginInvoke(loginName, password, assyncCallBack, assyncCall);

        }

        /// <summary>
        /// Executa a autenticação do usuário/senha.
        /// </summary>
        /// <param name="loginName">login do usuário</param>
        /// <param name="password">senha</param>
        /// <returns>Objeto <see cref="ValidationResults"/> com o resultado da validação</returns>
        private ValidationResults ValidateUser(string loginName, string password)
        {
            ValidationResults result = new ValidationResults();

            ///Criar uma instancia do SessionInformation indicando q a mesma nao devera receber mensagens,
            ///alteração realizada para resolver parte do problema de leak de memória na aplicação
            SessionInformation session = new SessionInformation(false);

            session.LoginName = loginName;
            session.Password = password;
            session.TokenId = Guid.NewGuid().ToString();

            Exception error = null;

            try
            {
                using (ServiceInstance<IAuthorization> si = new ServiceInstance<IAuthorization>())
                {
                    //Define as credenciais no CredentialRepository para ser acessada mais para frente.
                    CredentialRepository cr = CredentialRepository.GetInstance();
                    if (cr.UserInfo == null)
                        cr.UserInfo = new SinacorUserInfo();
                    cr.UserInfo.UserName = loginName;
                    cr.UserInfo.Password = password;

                    //Busca a Instancia do serviço passando a criptografia level 2.
                    IAuthorization service = si.TryGetInstance(session,CriptographyLevelType.Federated);
                    Infra.Authorization.Authorization.User user = service.AuthorizeUser();

                    result.User.Id = user.Id;
                    result.User.LoginName = user.LoginName;
                    result.User.Name = user.Name;
                    result.User.Password = password;
                    result.User.SessionId = user.SessionId;
                    result.IsAuthenticated = true;


                    //Armazena as informações do usuário no Credential Repository
                    cr.UserInfo.UserId = user.Id;
                    cr.UserInfo.SessionId = user.SessionId;
                }
            }
            catch (MessageSecurityException ex)
            {
                //Sinacor.Infra.Common.Logging.Logger.Write("", Sinacor.Infra.Common.Logging.LogType.Error, "", 0, "Infra", "ValidateUser", ex, "", "An error occurred when verifying security for the message", Sinacor.Infra.Common.Logging.eErrorType.ErroGeral);
                //if (ex.InnerException.Message == "An error occurred when verifying security for the message.")
                result.IsAuthenticated = false;

                if (ex.InnerException is FaultException &&
                    ((FaultException)ex.InnerException).Code.Name == "InvalidToken")
                {
                    Sinacor.Infra.UI.ErrorLogging.ErrorLogger.WriteLog(PSE.Framework.ErrorLogging.LogType.Warning, new Exception("Invalid user name or password", ex));

                    // Se não ocorreu um erro no serviço, verifica se a senha do usuário expirou
                    try
                    {
                        using (ServiceInstance<IUser> si = new ServiceInstance<IUser>())
                        {
                            IUser service = si.TryGetInstanceWithoutSecurity();
                            LoginRequest loginRequest = new LoginRequest() { Login = loginName, Password = password };

                            // Se login for válido, verifica se senha ainda é válida
                            if (service.ValidateUserLogin(loginRequest).ValidLogin)
                            {
                                //Verifica se usuário está bloqueado
                                result.IsBlocked = service.VerifyIsBlocked(loginRequest).IsBlocked;

                                //Caso nao esteja bloqueado, verifica se senha do usuário expirou
                                if (!result.IsBlocked)
                                    result.ChangePassword = service.VerifyChangePassword(loginRequest).ChangePassword;
                            }
                        }
                    }
                    catch (Exception changePasswordException)
                    {
                        HandleException("Error verifying if user is blocked and if password has expired", changePasswordException, result);
                    }
                }
                else
                {
                    HandleException("Error authenticating user, see server log for details", ex, result);
                }
            }
            catch (Exception ex)
            {
                HandleException("Error authenticating user", ex, result);
            }

            return result;
        }

        /// <summary>
        /// Método de callback da chamada assíncrona do método de Autenticação.
        /// </summary>
        /// <param name="ar"><see cref="IAsyncResult"/></param>
        private void ValidateUserCallBack(IAsyncResult ar)
        {
            //Recupera o delegate de execução do método de autenticação.
            Func<string, string, ValidationResults> assyncCall = (Func<string, string, ValidationResults>)ar.AsyncState;

            //Recupera o retorno da validação.
            _validationResults = assyncCall.EndInvoke(ar);

            //Efetua o tratamento necessário da validação.
            if (!_validationResults.IsAuthenticated && _validationResults.IsBlocked && !_validationResults.Faulted)
            {
                // Conta bloqueada
                _loginWindow.ShowBlockedAccount();
            }
            else if (!_validationResults.IsAuthenticated && _validationResults.ChangePassword && !_validationResults.Faulted)
            {
                // Senha expirada
                _loginWindow.ShowPasswordExpired();
            }
            else if (!_validationResults.IsAuthenticated && !_validationResults.ChangePassword && !_validationResults.Faulted)
            {
                // Login inválido
                _loginWindow.ShowInvalidLogin();
            }
            else if (_validationResults.Faulted)
            {
                // Erro na autenticação
                _loginWindow.ShowError(_validationResults.FaultedMessage, _validationResults.FaultedLogData);
            }
            else
            {
                _loginWindow.CloseWindow();
            }
        }
        #endregion

        #region Troca de senha
        void _loginWindow_ChangePassword(object sender, EventArgs e)
        {
            Func<string, ValidationResults> changePasswordCall = new Func<string, ValidationResults>(ChangePassword);
            AsyncCallback changePasswordCallBack = new AsyncCallback(ChangePasswordCallBack);

            //Executa a troca de senha de modo assincrono
            changePasswordCall.BeginInvoke(_loginWindow.LoginName, changePasswordCallBack, changePasswordCall);
        }

        private ValidationResults ChangePassword(string login)
        {
            ValidationResults result = new ValidationResults()
            {
                Faulted = false,
                IsAuthenticated = false
            };

            try
            {
                using (ServiceInstance<IUser> si = new ServiceInstance<IUser>())
                {
                    IUser service = si.TryGetInstanceWithoutSecurity();

                    LoginRequest request = new LoginRequest() { Login = login };

                    VerifyUserLoginResponse response = service.VerifyUserLogin(request);
                    result.IsAuthenticated = response.ValidLogin;
                }
            }
            catch (Exception ex)
            {
                HandleException("Error changing password", ex, result);
            }

            return result;
        }

        private void ChangePasswordCallBack(IAsyncResult ar)
        {
            //Recupera o delegate de execução do método de autenticação.
            Func<string, ValidationResults> assyncCall = (Func<string, ValidationResults>)ar.AsyncState;

            //Recupera o retorno da validação.
            ValidationResults result = assyncCall.EndInvoke(ar);

            //Efetua o tratamento necessário da validação.
            if (!result.Faulted && !result.IsAuthenticated)
                _loginWindow.ShowInvalidUser();
            else if (!result.Faulted && result.IsAuthenticated)
                _loginWindow.ShowChangePassword();
            else
                _loginWindow.ShowError(_validationResults.FaultedMessage, _validationResults.FaultedLogData);
        }
        #endregion

        #region Esqueci minha senha
        void _loginWindow_ForgotPassword(object sender, EventArgs e)
        {
            Func<string, ValidationResults> forgotPasswordCall = new Func<string, ValidationResults>(ForgotPassword);
            AsyncCallback forgotPasswordCallBack = new AsyncCallback(ForgotPasswordCallBack);

            //Executa a troca de senha de modo assincrono
            forgotPasswordCall.BeginInvoke(_loginWindow.LoginName, forgotPasswordCallBack, forgotPasswordCall);
        }

        private ValidationResults ForgotPassword(string login)
        {
            ValidationResults result = new ValidationResults()
            {
                Faulted = false,
                IsAuthenticated = false
            };

            try
            {
                using (ServiceInstance<IUser> si = new ServiceInstance<IUser>())
                {
                    IUser service = si.TryGetInstanceWithoutSecurity();

                    LoginRequest request = new LoginRequest() { Login = login };

                    VerifyUserLoginResponse response = service.VerifyUserLogin(request);
                    result.IsAuthenticated = response.ValidLogin;

                    if (result.IsAuthenticated)
                    {
                        result.PasswordReset = service.ResetPassword(request).PasswordReset;
                    }
                }
            }
            catch (Exception ex)
            {
                HandleException("Error resetting password", ex, result);
            }

            return result;
        }

        private void ForgotPasswordCallBack(IAsyncResult ar)
        {
            //Recupera o delegate de execução do método de autenticação.
            Func<string, ValidationResults> assyncCall = (Func<string, ValidationResults>)ar.AsyncState;

            //Recupera o retorno da validação.
            ValidationResults result = assyncCall.EndInvoke(ar);

            //Efetua o tratamento necessário da validação.
            if (!result.Faulted && !result.IsAuthenticated && !result.PasswordReset)
            {
                _loginWindow.ShowInvalidUser();
            }
            else if (!result.Faulted && result.IsAuthenticated && !result.PasswordReset)
            {
                _loginWindow.ShowInvalidEmail();
            }
            else if (!result.Faulted && result.IsAuthenticated && result.PasswordReset)
            {
                _loginWindow.ShowPasswordReset();
            }
            else
                _loginWindow.ShowError(_validationResults.FaultedMessage, _validationResults.FaultedLogData);
        }
        #endregion

        private void HandleException(string message, Exception ex, ValidationResults result)
        {
            Exception error = null;

            if (ex is EndpointNotFoundException ||
                ex is TimeoutException)
            {
                error = new Sinacor.Infra.UI.Exceptions.CommunicationException(ex);
            }
            else
            {
                error = new Exception(message, ex);
            }

            result.Faulted = true;

            if (error is Sinacor.Infra.UI.Exceptions.CommunicationException)
            {
                ErrorMessage.ConfigureExceptionMessage(error);
                result.FaultedMessage = error.Message;
            }
            else
            {
                result.FaultedMessage = (string)ResourceProviderManager.GetResourceProvider().TryGetResource("Infra.Controls.ExceptionMessage.AuthenticationFailed");
            }

            PSE.Framework.ErrorLogging.LogData logData = Sinacor.Infra.UI.ErrorLogging.ErrorLogger.WriteLog(PSE.Framework.ErrorLogging.LogType.Error, error);

            result.FaultedLogData = logData;
        }

        #region IAuthenticationProvider Members

        /// <summary>
        /// Inicia a Autenticação do usuário.
        /// </summary>
        /// <returns>true se o usuário foi autenticado com sucesso, caso contrário retorna false</returns>
        public bool LogonUser()
        {
            return LogonUser(String.Empty);
        }

        /// <summary>
        /// Inicia a Autenticação do usuário.
        /// </summary>
        /// <returns>true se o usuário foi autenticado com sucesso, caso contrário retorna false</returns>
        public bool LogonUser(string loginName)
        {
            AuthenticateUser(loginName);

            if (_validationResults != null)
            {
                _user = _validationResults.User;
                return _validationResults.IsAuthenticated;
            }
            else
                return false;
        }

        /// <summary>
        /// Retorna a Informação do usuário autenticado.
        /// </summary>
        /// <returns><see cref="Sinacor.Infra.UI.Authentication.User"/></returns>
        public User GetUserInformation()
        {
            return _user;
        }

        /// <summary>
        /// Retorna o resultado da validação.
        /// </summary>
        /// <returns>Objeto <see cref="ValidationResults"/> com o resultado da validação</returns>
        public ValidationResults GetValidationResults()
        {
            return _validationResults;

        }

        #endregion
    }
}


